#pragma once

#include <iostream>
#include <functional>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

namespace Server
{
    using namespace std;

    const static string defaultIP = "0.0.0.0";

    const static int gnum = 1024;

    enum
    {
        USAGE_ERR = 1,
        SOCK_ERR,
        BIND_ERR,
        OPENFILE_ERR,
        CUT_ERR
    };

    class udpServer
    {
    public:
        typedef function<void (int, string, uint16_t, string)> func_t;

    public:
        udpServer(const func_t &callback, const uint16_t &port, const string &ip = defaultIP)
            : _callback(callback), _sockfd(-1), _ip(ip), _port(port)
        {
        }

        ~udpServer()
        {
        }

        void initServer()
        {
            // 创建套接字(文件)
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd == -1)
            {
                cerr << "socket error: " << errno << " -> " << strerror(errno) << endl;
                exit(SOCK_ERR);
            }
            cout << "socket success : " << _sockfd << endl;

            // 绑定ip + port(网络)
            struct sockaddr_in local;

            local.sin_family = AF_INET;
            local.sin_port = htons(_port);                      // ip和port都要发送过去，所以要从主机序列转为网络序列(大端)
            // local.sin_addr.s_addr = inet_addr(_ip.c_str());  // 1.string->uint32_t   2.htonl()
            local.sin_addr.s_addr = htonl(INADDR_ANY);          // bind任意地址，服务器真实写法

            // 未来服务器需要明确自己的端口号，不能轻易改变
            int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
            if (n == -1)
            {
                cerr << "bind error: " << errno << " -> " << strerror(errno) << endl;
                exit(BIND_ERR);
            }
        }

        void start()
        {
            cout << "server started!" << endl;

            // 服务器本质就是一个死循环(操作系统)
            char buffer[gnum];
            for(;;)
            {
                // 读取数据
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);   // 必填
                ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
                
                if(s > 0)
                {
                    string ClientIp = inet_ntoa(peer.sin_addr);             // 1.网络序列->主机序列     2.uint32_t->string
                    uint16_t ClientPort = ntohs(peer.sin_port);             // 网络序列->主机序列 
                    buffer[s] = '\0';
                    string msg = buffer;

                    // cout << ClientIp << "[" << ClientPort << "]# " << msg << endl;
                    _callback(_sockfd, ClientIp, ClientPort, msg);
                }
            }
        }

    private:
        func_t _callback;
        int _sockfd;
        string _ip;     // 实际上，一款网络服务器，不建议指明一个特定IP，因为如果我这台机器上有多个IP，那从其他IP来的数据包我就有可能都丢弃了
        uint16_t _port;
    };
}